문서의 임의 삭제는 제재 대상으로, 문서를 삭제하려면 삭제 토론을 진행해야 합니다. 문서 보기문서 삭제토론 디자인 패턴 (문단 편집) === 옵저버(Observer) === 하나의 객체가 그 외의 여러 객체에 영향을 미칠 경우, 그 객체들 간에 일일이 연결(커플링)을 만드는 대신 그 여러 객체들이 하나의 객체를 '관찰'하는 패턴을 만들어 중점이 되는 개체가 변화할 때 그 변화를 연결된 여러 옵저버들에게 전달하는 패턴을 의미한다. RPG 게임을 예로 들어보자. RPG의 플레이어에게는 "HP"의 개념이 존재한다. 그런데 어떤 RPG 게임에서 이 HP를 알려주는 오브젝트가 총 3개가 있다고 하자. 하나는 캐릭터 위에 뜨는 체력 게이지, 하나는 인터페이스에 표시되는 체력 게이지, 나머지 하나는 그 체력 게이지 위에 표시되는 상세 수치이다. 만약 플레이어의 HP가 늘어나거나 혹은 줄어들면, 이 3개의 오브젝트 역시 각각의 수치를 반영하여 새로운 형태를 갱신해야 할 것이다. HP를 나타내는 변수가 플레이어라는 클래스 안에 있다고 했을 때, HP의 변화에 따라 각종 인터페이스의 상태가 달라지는 코딩을 해 보자. 그러면, {{{#!syntax csharp public class MainUI{ public Image HPBar; public Image HPNumber; public void HPChange(int HP){ /* HPBar과 HPNumber를 HP에 맞게 수정 */} // 기타 여러 코드 } public class CharacterUI{ public Image SimpleHPBar; public void HPChange(int HP){ /* SimpleHPBar를 HP에 맞게 수정 */} // 기타 여러 코드 } public class Player{ private int HP; public MainUI mainUI; public CharacterUI characterUI; public void ChangeHPUI(){ mainUI.HPChange(HP); characterUI.HPChange(HP); } } }}} 이런 식으로 Player와 각각의 UI를 잇는 ChangeHPUI라는 함수를 만들어서 HP가 바뀔 때마다 함수를 호출하여 각각의 UI를 '직접 지정해서' 바꾸게 만들면 작동은 한다. 그러나 이 코드는 OCP, 즉 개방 폐쇄 원칙을 위반한다. 이러한 직접적인 HP 수치 - HP 인터페이스 간의 객체 연결은, 만약에 나중에 HP바와 연동해야 하는 다른 함수(파티 인터페이스를 추가해서 HP 게이지가 하나 더 생긴다든지, 이벤트 도중이라 캐릭터 위의 HP 게이지가 일시정지 된다든지)가 생길 때마다 이 Player라는 클래스 자체를 수정해야만 한다. 게다가 이 함수는 SRP도 위반한다. Player는 그냥 HP를 비롯한 플레이어에 대한 정보에만 책임이 있어야 하는데, 이 코딩대로라면 앞으로 무슨 인터페이스가 추가로 생기거나 없어질 때마다 수정해야 할 책임이 생긴다. 이런 상황의 경우 Player라는 클래스 내부에서 굳이 저 HP 게이지를 바꾸는 함수를 호출할 필요가 없다. Player는 그저 자신의 HP가 바뀌었다는 정보만 전달해주면 되고, 그에 따라 시각적인 변화가 생기는 것은 각각의 HP 게이지를 담당하는 클래스에서 실행해야 할 일이다. 플레이어는 HP의 변화에 발맞춰 움직여야 할 다른 클래스에게 'HP의 수치를 전달'하기만 하면 되고, 그 다른 클래스들은 '바뀌었다는 사실만 통해 전달받아 알맞게 실행'하기만 하면 된다. 저 Player 클래스가 앞으로 HP를 전달해야 할 클래스가 더 생기든 사라지든 단지 정보를 보내주기만 하는 역할만 하면 될 뿐 수정을 가할 필요가 없다. 옵저버 패턴은 이렇듯이 '1개의 클래스(이하 서브젝트)'가 '여러 개의 클래스(이하 옵저버)'와 연결되어 있을 때( = 다대일 의존성), 이 클래스들 간의 연결을 최소화하기 위해 서브젝트가 옵저버에게 정말 필요한 정보, 즉 알림만을 여러 개의 클래스에 보내주는 패턴을 의미한다. 옵저버 패턴은 여러 매체 플랫폼에서 사용하는 '구독' 기능과 비슷하다. 한 명의 '창작자'를 여러 '독자'가 구독하고, 창작자가 새로운 매체를 올리면 구독자에게 그 사실을 전송한다. 구독에 따라 창작자 - 독자 간의 연결은 생길 수도 있고 사라질 수도 있다. 그러나 창작자는 각 독자가 뭐 하는 사람인지, 독자는 창작자가 뭐 하는 사람인지 직접적으로 관여하지 않는다. 다만 구독 시스템은 창작자를 관찰(Observe)하여 알릴 것이 있는지만 판독하고, 있다면 그걸 독자들에게 방송(Broadcasting)할 뿐이다. 구독 시스템을 생각하며 서브젝트와 옵저버가 갖춰야 할 기능들을 생각해보자. 서브젝트에는 자신과 연결된 옵저버를 상황에 따라 연결하거나 빼는 '옵저버 추가', '옵저버 제거', 그리고 무엇보다 정보를 알려줄 '옵저빙'이 있다. 반면 옵저버의 경우 '정보를 전달받았을 때 해야 할 일'이라는 1가지 기능만 있으면 된다. 이를 통해서 이하와 같은 Subject와 Observe 인터페이스를 만들어준다. {{{#!syntax csharp public interface Subject{ public void AddObserver(Observer ops); public void RemoveObserver(Observer ops); public void NotifyToObserver(); } public interface Observer{ public void Notified(int nowHP); } }}} 여기서 Notified 함수의 매개 변수는 전달해야 할 필요에 따라 다르다. 단순히 전달만 하고 싶으면 매개변수가 없어도 되고, 본 예제에서는 서브젝트(플레이어)의 HP를 전송하는 게 목적이므로 int 매개변수로 HP를 전송한다. 이제 이 Interface들을 통해 서브젝트와 옵저버를 구현화해보자. 먼저 각각의 UI는 {{{#!syntax csharp public class MainUI : Observer{ public Image HPBar; public Image HPNumber; public void Notified(int HP){ /* HPBar과 HPNumber를 HP에 맞게 수정 */} // 기타 여러 코드 } public class CharacterUI : Observer{ public Image SimpleHPBar; public void Notified(int HP){ /* SimpleHPBar를 HP에 맞게 수정 */} // 기타 여러 코드 } }}} 같은 방식으로 Observer로서 HP를 전달받으면 UI가 바뀔 수 있게 하자. 이제 Player에 Subject 인터페이스를 상속시키고, 각 UI와 연결해주면 된다. {{{#!syntax csharp public class Player : Subject{ private int HP; private List observers = new List(); // 이 서브젝트가 관리중인 오브젝트의 목록 public AddObserver(Observer ops){ observers.Add(ops); } // 옵저버 리스트에 옵저버 추가하기 public RemoveObserver(Observer ops){ if (observers.IndexOf(observer) > 0) { observers.Remove(observer); } } // 옵저버 리스트에 그 옵저버가 있으면 옵저버 빼기 public NotifyToObserver(){ foreach (Observer ops in observers) { ops.Notified(HP); } } // 옵저버 리스트의 모든 옵저버에게, 현재 HP를 매개변수로 하여 전달 } }}} 이렇게 만들어놓고 추후 코드를 통해 AddObserver(mainUI), AddObserver(characterUI) 등으로 각 UI를 플레이어와 서브젝트 - 옵저버의 관계로 맺어주기만 하면 된다. 이제 NotifyToObserver()을 실행하기만 하면 플레이어 클래스는 알아서 정보를 옵저버들에게 전달하고, 옵저버들은 전달받은 정보를 통해 새로운 UI를 만들어낼 것이다. 이를 통해 플레이어와 UI를 서브젝트 - 옵저버 관계로 맺어서 최소한의 결합성으로 필요한 정보를 전달하고, 나중에 UI를 수정해야 할 때에도 Player 클래스 자체를 수정할 필요는 전혀 없이 옵저버를 넣거나 빼기만 하면 된다.저장 버튼을 클릭하면 당신이 기여한 내용을 CC-BY-NC-SA 2.0 KR으로 배포하고,기여한 문서에 대한 하이퍼링크나 URL을 이용하여 저작자 표시를 하는 것으로 충분하다는 데 동의하는 것입니다.이 동의는 철회할 수 없습니다.캡챠저장미리보기